package api.v2;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.XmlRpcRequest;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.client.XmlRpcClientException;
import org.apache.xmlrpc.client.XmlRpcSunHttpTransport;
import org.apache.xmlrpc.client.XmlRpcSunHttpTransportFactory;
import org.apache.xmlrpc.client.XmlRpcTransport;
import org.apache.xmlrpc.client.XmlRpcTransportFactory;

/**
 * Testing XML-RPC client with Drupal7. This example uses xml-rpc with
 * methodology for calling remote drupal functionality as presented by
 * "Services 3.X"
 * <p/>
 * For Xml-Rpc I use "org.apache.xmlrpc.client" library with depandencies <a
 * href="http://ws.apache.org/xmlrpc/client.html">http://ws.apache.org/xmlrpc/
 * client.html</a>.
 */
public class Drupal7XmlRpcClientTest {

	/**
	 * Name of remote functions used by this example
	 */
	public static final String METHOD_SYSTEM_CONNECT = "system.connect";
	public static final String METHOD_USER_LOGOUT = "user.logout";
	public static final String METHOD_USER_LOGIN = "user.login";
	public static final String METHOD_USER_CREATE = "user.create";
	public static final String METHOD_FILE_SAVE = "file.save";
	public static final String METHOD_NODE_RETRIEVE = "node.retrieve";
	public static final String METHOD_NODE_CREATE = "node.create";

	private Logger log = Logger.getLogger(Drupal7XmlRpcClientTest.class.getName());

	/**
	 * Endpoint is defined within Drupal services module configuration in order
	 * to define a URL that is avilable for serving a specific set of service
	 * calls. See drupal documentation for "Services 3.X". <a
	 * href="http://drupal.org/node/783236">http://drupal.org/node/783236</a>
	 */
	private final String endpointURL;

	/**
	 * Xml-Rpc clinet object
	 */
	private XmlRpcClient xmlRpcClient;

	/**
	 * Cookie value create upon login. We provide it only as an info. User does
	 * not have to handle setting of Cookie. Instead, the {@link #login} method
	 * do it upon successful login, and allow any following service call, to be
	 * handled in the scope of the login user.
	 * 
	 * @see #login(String, String)
	 */
	private String cookie;

	/**
	 * Initialize the XmlRpcClient client with basic configuration of endpoint
	 * URL. The serviceURL must be a valid URL.
	 * 
	 * @param endpointURL
	 *            the URL of the Drupal service's endpoint. example:
	 *            http://www.example.com/drupal/test_endpoint
	 * @throws java.net.MalformedURLException
	 *             if the serviceURL is not a URL
	 */
	public Drupal7XmlRpcClientTest(String endpointURL) throws MalformedURLException {
		this.endpointURL = endpointURL;
		log.setLevel(Level.INFO);

		// create the Xml-Rpc clinet object.
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		config.setServerURL(new URL(this.endpointURL));
		xmlRpcClient = new XmlRpcClient();
		xmlRpcClient.setConfig(config);
	}

	/**
	 * Call system.connect
	 * 
	 * @throws Exception
	 *             if fail
	 */
	public void connect() throws Exception {
		try {
			Map response = (Map) xmlRpcClient.execute(METHOD_SYSTEM_CONNECT, new Object[] {});
			log.info("Connected to server using SessionID: " + response.get("sessid"));
		} catch (Exception x) {
			throw new Exception("cannot connect to " + endpointURL + ": " + x.getMessage(), x);
		}
	}

	/**
	 * Call user.login
	 * 
	 * @param username
	 *            user name
	 * @param password
	 *            password
	 * @return Map with details of login user (response.get("user")) and login
	 *         session-name and session-id that are used internally to construct
	 *         the Cookie used by following calls.
	 * @throws Exception
	 *             if operation fail
	 */
	public Map login(String username, String password) throws Exception {
		// Add Login Paramaters
		Vector<Object> params = new Vector<Object>();
		params.add(username);
		params.add(password);
		Map response = (Map) xmlRpcClient.execute(METHOD_USER_LOGIN, params);

		// The user.login call return two attributes in which we use to
		// construct value for a "Cookie" header.
		// With then set xmlRpcClient with new XmlRpcTransportFactory that set
		// 'Cookie' header using the composed cookie value
		cookie = response.get("session_name") + "=" + response.get("sessid");
		XmlRpcTransportFactory factory = new XmlRpcSunHttpTransportFactory(xmlRpcClient) {
			public XmlRpcTransport getTransport() {
				return new XmlRpcSunHttpTransport(xmlRpcClient) {
					@Override
					protected void initHttpHeaders(XmlRpcRequest request) throws XmlRpcClientException {
						super.initHttpHeaders(request);
						setRequestHeader("Cookie", cookie);
					}
				};
			}
		};
		xmlRpcClient.setTransportFactory(factory);

		return response;
	}

	/**
	 * Call user.logout
	 * 
	 * @throws Exception
	 *             if operatino fail
	 */
	public void logout() throws Exception {
		Vector<Object> params = new Vector<Object>();
		xmlRpcClient.execute(METHOD_USER_LOGOUT, params);
		log.info("Logout Sucessfull");
	}

	/**
	 * Call user.create that is equivalent to user.register.
	 * <p/>
	 * On the server side this call handle user regitration by emulating user's
	 * registration form. It means that basic fields 'name' , 'pass', 'mail'
	 * MUST be provided into and 'account' object as well as all other Required
	 * custom fields. In this case there aretwo such custom fields required per
	 * user registration: 'field_newsletter' and 'field_accept_terms'
	 * <p/>
	 * Important note: on Drupal to 'pass' value provided by form is relevant
	 * only when login user is "admin" by permissions or that
	 * user_email_verification is not set to TRUe
	 * 
	 * <pre>
	 * Taken from "user.module" file:
	 * <p/>
	 * if (!variable_get('user_email_verification', TRUE) || $admin) {
	 * $pass = $form_state['values']['pass'];
	 * }
	 * </pre>
	 * <p/>
	 * In short! if you plan to set custom password by xml-rpc call, make sure
	 * that login user is admin.
	 * 
	 * @param name
	 *            name
	 * @param pass
	 *            password
	 * @param email
	 *            email
	 * @return Map with details of createduser
	 * @throws org.apache.xmlrpc.XmlRpcException
	 *             if operaton fail
	 */
	public Map createUser(String name, String pass, String email) throws XmlRpcException {
		HashMap<String, Object> account = new HashMap<String, Object>();
		account.put("name", name);
		account.put("pass", pass);
		account.put("mail", email);
		account.put("status", "1"); // indicate if user will become active or
									// not
		account.put("notify", "1"); // indicate ifnotification should be sent

		account.put("field_newsletter", prepareCheckboxValueParam("1")); // this
																			// is
																			// custom
																			// field
		account.put("field_accept_terms", prepareCheckboxValueParam("1")); // this
																			// is
																			// custom
																			// field

		// Add Login Paramaters
		Vector<Object> params = new Vector<Object>();
		params.add(account);

		log.info("Creating user");
		return (Map) xmlRpcClient.execute(METHOD_USER_CREATE, params);
	}

	/**
	 * Checkbox value is set into 3-level associate array like data structure
	 * <p/>
	 * Example of what is expected in Drupal server-side for a custom field of
	 * type "checkbox"
	 * 
	 * <pre>
	 * array(1) {
	 *      ["und"]=> array(1) {
	 *          [0]=> array(1) {
	 *              ["value"]=> int([value])
	 *          }
	 *      }
	 * }
	 * </pre>
	 * 
	 * @param value
	 *            the value to cset for the checkbox - for example "1" or "0"
	 *            depanding on field definition on the specific drupal
	 *            applicationl
	 * @return Map of params constructed for checkbox field
	 */
	public static HashMap<String, HashMap> prepareCheckboxValueParam(String value) {
		HashMap<String, String> arr111 = new HashMap<String, String>();
		arr111.put("value", value);

		HashMap<String, HashMap> arr11 = new HashMap<String, HashMap>();
		arr11.put("0", arr111);

		HashMap<String, HashMap> arr1 = new HashMap<String, HashMap>();
		arr1.put("und", arr11);
		return arr1;
	}

	public Map<String, Object> retrieveNode(int nid) throws XmlRpcException {
		HashMap<String, Object> node = new HashMap<String, Object>();
		node.put("nid", "" + nid);
		Vector<Object> params = new Vector<Object>();
		params.add(node);
		Map<String, Object> response = (Map<String, Object>) xmlRpcClient.execute(METHOD_NODE_RETRIEVE, params);

		// get individual fields of the node
		String name = (String) response.get("title");
		// TODO change for our parameters
		// String fieldValue = (String) getFieldData(response, "field_myfield");

		return response;
	}

	public Map<String, Object> createPage(int nid) throws XmlRpcException {
		HashMap<String, Object> node = new HashMap<String, Object>();
		node.put("nid", "" + nid);
		Vector<Object> params = new Vector<Object>();
		params.add(node);
		Map<String, Object> response = (Map<String, Object>) xmlRpcClient.execute(METHOD_NODE_CREATE, params);

		// get individual fields of the node
		String name = (String) response.get("title");
		// TODO change for our parameters
		// String fieldValue = (String) getFieldData(response, "field_myfield");

		return response;
	}

	public Object getFieldData(Map<String, Object> response, String fieldName) {
		Object[] obj = (Object[]) ((Map<String, Object>) response.get(fieldName)).get("und");
		return ((Map<String, Object>) obj[0]).get("value");
	}

	/**
	 * Testing flow: 1. Connect <br/>
	 * 2. Login <br/>
	 * 3. Create user <br/>
	 * 4. Logout <br/>
	 * 
	 * @param args
	 *            not used
	 */
	public static void main(String[] args) {
		String endpointURL = "http://ec2-23-20-161-202.compute-1.amazonaws.com/test"; // Set
																						// your
																						// own
		// http://[drupal
		// host]/[endpoint]
		// URL
		String adminName = "carlos.baez"; // Set your own admin user name
		String adminPass = "noosferalabdoo"; // Set your own admin user password

		// Basic details of sample new user
		String newUserName = "kuku2";
		String newUserPass = "123456";
		String newUserEmail = "kuku2@mail";

		try {
			Drupal7XmlRpcClientTest service = new Drupal7XmlRpcClientTest(endpointURL);

			// 1) connect
			service.connect();

			// 2) login with admin
			Map login = service.login(adminName, adminPass);
			// you can create "watch" on 'login' to see the details of login
			// user (the admin user)

			Map<String, Object> info = service.retrieveNode(2);

			System.out.println(info);

			// 3) create new user
			// Map userData = service.createUser(newUserName, newUserPass,
			// newUserEmail);
			// you can create "watch" on 'userData' to see the "uid" of new
			// created user

			// 4) logout
			service.logout();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
